Although Go language is not highly regarded in terms of language design by Wang Yin, its concise code structure is indeed captivating.
Go language statements do not require a ;
at the end.
Use var
to declare variables. When variables need to be initialized, you can use the assignment symbol :=
instead of =
to omit the var
keyword.
var a int
var b string
var c int = 10
var d = "golang" // The compiler automatically infers the type
d := 10
Unlike C or Java, Go language’s type declaration is on the right side of the variable. Note that if declared variables in the program are not used, the program will not compile. Go is an engineering-oriented language, so some of its features might seem unreasonable but will improve efficiency in actual engineering.
Go’s variable assignment supports some cool writing styles, such as swapping the values of variables x and y using this counter-intuitive method:
x, y = y, x
In Go, const
is used to define constants. true
, false
, and iota
are predefined constants. Among them, iota
is a bit special. iota
resets to 0 each time the const
keyword appears, and before the next const
appears, the value of iota
increments by 1 each time it appears.
const a = iota // 0
const b = iota // 0
const (
c = iota // 0
d = iota // 1
)
Declare an array of 3 elements and initialize it:
array := [3]int{0, 1, 2}
array[0] = 3
fmt.Println(array)
Like other languages, Go cannot change the size of an array after declaration. Therefore, Go provides slices similar to Python. Slices can be created from arrays or using the make()
function.
array := [3]int{0, 1, 2}
slice1 := array[:2] // Created from an array
slice2 := make([]int, 3) // Created directly
fmt.Println(slice1) // [0 1]
fmt.Println(slice2) // [0 0 0]
In addition to slices, maps are also created using the make
function. The full type declaration of a map is var myMap map[string]int
, meaning the variable myMap
has a key of type string
and a value of type int
.
Go allows if-else
statements without parentheses around the condition, although adding them is also fine.
a := 1
if a == 1 {
print(1)
} else if (a == 2) {
print(2)
} else {
print(3)
}
switch
statements do not require parentheses around the condition, nor do they require a break
. Other matches will not execute, similar to Scala. Optimizing switch
statements seems to be a common practice.
i := 0
switch i {
case 0:
print(0)
case 1:
print(1)
}
Loop conditions also do not require parentheses. Go only supports for
loops and has optimized for infinite loops, no longer requiring the for(;;)
syntax.
for {
print(1)
}
Go language originates from the C language faction, so from the beginning, Go is not an OOP or FP language and does not have the concept of classes or objects. Functions are first-class citizens in the program. Like in C, the main
function (under the main
package) is the entry point of the entire program.
func add(a int, b int) (int, int) {
return a + b, a - b
}
func main() {
x, y := add(1, 2)
print(x, y)
}
Go statements are concise and efficient, with the first parenthesis after the function name for parameters and the second for return values. Functions support multiple return values. If parameter types are the same, their type declarations can be combined, such as (a, b int)
.
Although Go does not have classes or objects, it does have powerful structs similar to those in C. Here, we define a Person
struct with two properties name
and age
, and add a method getInfo
to Person
to output information about a Person
object:
type Person struct {
name string
age int
}
func (p Person) getInfo() {
print(p.name, p.age)
}
func main() {
smallyu := new(Person)
smallyu.name = "smallyu"
smallyu.age = 1
smallyu.getInfo()
}
Understanding such a program with OOP thinking is not inappropriate. In addition to structs, Go also retains the concept of pointers. Java programmers might be a bit unfamiliar with pointers. Regarding the application of pointers in struct methods, a simple example can help understand:
type Person struct {
name string
}
func (p Person) setName() {
p.name = "set name"
}
func (p *Person) setName2() {
p.name = "set name"
}
func main() {
smallyu := &Person{"smallyu"}
smallyu.setName()
fmt.Println(smallyu) // &{smallyu}
bigyu := &Person{"bigyu"}
bigyu.setName2()
fmt.Println(bigyu) // &{set name}
}
Methods defined with value types have parameters as formal parameters; methods defined with reference types have parameters as actual parameters. &{}
is one way to initialize objects, equivalent to new()
.
The concept of anonymous composition in Go is equivalent to inheritance in OOP languages. A struct can inherit the properties and methods of another struct, roughly like this.
type Father struct {
name string
}
func (f Father) getName() {
print(f.name)
}
type Son struct {
Father
}
func main() {
smallyu := &Son{}
smallyu.name = "smallyu"
smallyu.getName() // smallyu
}
Son
does not define the name
property or the getName()
method; they are inherited from Father
.
Go’s interfaces are non-intrusive. As long as a struct implements all the methods in an interface, the program will consider the struct to have implemented that interface.
type IPerson interface {
getName()
}
type Person struct {
name string
}
func (p Person) getName() {
print(p.name)
}
func main() {
var smallyu IPerson = &Person{"smallyu"}
smallyu.getName()
}
The keyword for using goroutines is go
. The naming itself reflects the importance of goroutines to Go. Goroutines are lightweight threads, and starting a goroutine is very simple:
func f(msg string) {
println(msg)
}
func main() {
f("direct call")
go f("goroutine call")
}
Running the program, you will find it only prints “direct call”. Does this seem familiar? go
starts another “thread” to print the message, while the main thread has already ended. Adding fmt.Scanln()
at the end of the program to prevent the main thread from ending will show all the print content.
Channels are the means of communication between goroutines.
func main() {
message := make(chan string)
go func() {
message <- "ping"
}()
msg := <-message
println(msg)
}
The make
function returns a chan string
type channel. In the anonymous function, the string “ping” is sent into the channel, and then the data in the channel is output to the variable msg
, finally printing the value of msg
as “ping”.
In Go, two functions are commonly used for error handling: the panic
function and the defer
function. The panic
function prints an error message and terminates the entire program, similar to Java’s Throw Exception; the defer
function will execute before the current context ends, similar to finally after try-catch; although the panic
function terminates the entire program, it does not terminate the execution of the defer
function, which can be used to print logs. Here is a simple example:
func main() {
println("beginning")
defer func() {
println("defer")
} ()
println("middle")
panic("panic")
println("ending")
}
Let’s analyze the program’s execution result. First, beginning
is printed; then defer
is encountered and not printed yet; middle
is printed before defer
; encountering panic
, the program will terminate, printing defer
and panic
.
Note that defer
is executed before the program ends, not